//
// File-system system calls.
// Mostly argument checking, since we don't trust
// user code, and calls into file.c and fs.c.
//


#include "include/types.h"
#include "include/riscv.h"
#include "include/defs.h"
#include "include/param.h"
#include "include/stat.h"
#include "include/spinlock.h"
#include "include/proc.h"
// #include "include/fs.h"
#include "include/sleeplock.h"
#include "include/file.h"
#include "include/fcntl.h"
#include "include/fat32.h"
#include "include/memlayout.h"
#include "include/debug.h"
// Fetch the nth word-sized system call argument as a file descriptor
// and return both the descriptor and the corresponding struct file.
static int
argfd(int n, int *pfd, struct file **pf)
{
  int fd;
  struct file *f;

  if(argint(n, &fd) < 0)
    return -1;
  if(fd < 0 || fd >= NOFILE || (f=myproc()->ofile[fd]) == 0)
    return -1;
  if(pfd)
    *pfd = fd;
  if(pf)
    *pf = f;
  return 0;
}

// Allocate a file descriptor for the given file.
// Takes over file reference from caller on success.
// 在当前进程里，为文件f，分配一个新的文件标识符fd。
static int
fdalloc(struct file *f)
{
  int fd;
  struct proc *p = myproc();

  for(fd = 0; fd < NOFILE; fd++){
    if(p->ofile[fd] == 0){
      p->ofile[fd] = f;
      return fd;
    }
  }
  return -1;
}

uint64
sys_dup(void)
{
  // test-user:syscall(SYS_dup, fd);
  //                            0                 
  // xv6-user: dup(int oldfd)
  //                     0        
  struct file *f;
  int fd;

  if(argfd(0, 0, &f) < 0)
    return -1;
  if((fd=fdalloc(f)) < 0)
    return -1;
  filedup(f);
  return fd;
}
uint64
sys_dup3(void)
{
  // test-user:(SYS_dup3, oldfd, newfd_number, 0);
  //                         0       1          
  
  struct file *f;
  int newfd_number;
  int oldfd;
  if(argfd(0, &oldfd, &f) < 0)
    return -1;
  if(argint(1, &newfd_number) < 0)
    return -1;  

  struct proc *p = myproc();
  
  p->ofile[newfd_number] = f;
  
  filedup(f);
  __debug_info("dup3", "oldfd = %d, newfd = %d\n", oldfd, newfd_number);
  return newfd_number;

}
uint64
sys_read(void)
{
  // user-test: syscall(SYS_read, fd, buf, len);
  //                              0,   1,   2 
  // xv6-user: read(fd, buf, sizeof(buf))
  //                 0,    1,   2

  struct file *f;
  int n;
  uint64 p;

  if(argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argaddr(1, &p) < 0)
    return -1;
  return fileread_user(f, p, n);
}

uint64
sys_write(void)
{
  struct file *f;
  int fd, n;
  uint64 p;

  if(argfd(0, &fd, &f) < 0 || argint(2, &n) < 0 || argaddr(1, &p) < 0)
    return -1;
  __debug_info("write", "fd = %d, len = %d\n", fd, n);
  return filewrite_user(f, p, n);
}

uint64 sys_writev()
{
  // prototype: ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
  int iovcnt;
  uint64 iov;
  struct file *f;
  if (argfd(0, 0, &f) < 0 ||  argaddr(1, &iov) < 0 || argint(2, &iovcnt) < 0)
    return -1;
  if (iovcnt > 10)
    return -1;
  struct iovec vec[10];
  // copy from user addr iov to kernel vec
  if (copyin(myproc()->pagetable, (char *)vec, iov, iovcnt * sizeof(struct iovec)) < 0)
    return -1;
  int nwrite = 0;
  for (int i = 0; i < iovcnt; i ++) {
    if (filewrite_user(f, (uint64)vec[i].iov_base, vec[i].iov_len) != -1) {
      nwrite += vec[i].iov_len;
    }
  }
  return nwrite;
}

uint64
sys_close(void)
{

  // test-user: syscall(SYS_close, fd);
  //                               0
  // xv6-user: close(fd);
  //                 0
  int fd;
  struct file *f;

  if(argfd(0, &fd, &f) < 0)
    return -1;
  myproc()->ofile[fd] = 0;
  fileclose(f);
  __debug_info("close", "close fd = %d\n", fd);
  return 0;
}

uint64
sys_fstat_xv6(void)
{
  struct file *f;
  uint64 st; // user pointer to struct stat

  if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
    return -1;
  return filestat_xv6(f, st);
}

uint64
sys_fstat(void)
{
  // int fstat(int fd, struct stat *statbuf);
  struct file *f;
  uint64 addr; // user pointer to struct stat
  int fnum;
  if(argfd(0, &fnum, &f) < 0 || argaddr(1, &addr) < 0) {
    return -1;
  }
  __debug_info("fstat", "file descriptor num = %d\n", fnum);
  __debug_info("fstat", "f = 0x%x\n", f);
  __debug_info("fstat", "filename = %s\n", f->ep->filename);
  return filestat(f, addr);

}

uint64 sys_fstatat(void)
{
  // int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
  int dirfd, flags;
  uint64 uaddr;
  char filepath[MAXPATH];
  if (argint(0, &dirfd) < 0 || argstr(1, filepath, MAXPATH) < 0 || argaddr(2, &uaddr) < 0 || argint(3, &flags) < 0) {
    __debug_error("fstatat", "arg fetch error\n");
  }
  if (dirfd != -100) {
    __debug_error("fstatat", "now only support dirfd = -100\n");
  }
  // First: open this file
  struct dirent *ep;
  // find dir entry of filepath
  __debug_info("fstatat", "filepath = %s\n", filepath);
  if ((ep = ename(filepath)) == 0) {
    __debug_error("fstatat", "can't find filepath\n");
    return -1;
  }
  elock(ep);
  // TODO: Temporary ignore flag
  if (ep->attribute & ATTR_DIRECTORY) {
    __debug_info("fstatat", "ep->attribute & ATTR_DIRECTORY\n");
  }
  // alloc file structure
  struct file *f ;
  if ((f = filealloc()) == 0) {
    eunlock(ep);
    eput(ep);
    __debug_error("fstatat", "filealloc\n");
    return -1;
  }
  f->type = FD_ENTRY;
  f->off = 0;
  f->ep = ep;
  f->readable = 1;
  f->writable = 1;
  // alloc file descriptor
  int fd;
  if ((fd = fdalloc(f)) < 0){
    eunlock(ep);
    fileclose(f);
    __debug_error("fstatat", "fd = fdalloc(f)\n");
    return -1;
  }
  eunlock(ep);
  return filestat(f, uaddr);
}
/* get directory entries */
uint64 sys_getdents64(void)
{
  /* ssize_t getdents64(int fd, void *dirp, size_t count) */
  struct file *f;
  int count, fnum;
  uint64 addr; // user pointer to struct linux_dirent
  if(argfd(0, &fnum, &f) < 0 || argaddr(1, &addr) < 0 || argint(2, &count) < 0) {
    return -1;
  }
  return readdir(f, addr, count);
}
static struct dirent*
create(char *path, short type)
{
  struct dirent *ep, *dp;
  char name[FAT32_MAX_FILENAME + 1];

  if((dp = enameparent(path, name)) == 0)
    return 0;

  elock(dp);
  if((ep = ealloc(dp, name, type == T_DIR)) == 0){
    eunlock(dp);
    eput(dp);
    return 0;
  }

  elock(ep);

  // what needs to do ?

  eunlock(dp);
  eput(dp);

  return ep;
}

uint64
sys_open(void)
{
  char path[FAT32_MAX_PATH + 1];
  int fd, omode;
  struct file *f;
  struct dirent *ep;

  if(argstr(0, path, FAT32_MAX_PATH + 1) < 0 || argint(1, &omode) < 0)
    return -1;

  if(omode & O_CREAT){
    ep = create(path, T_FILE);
    if(ep == 0){
      return -1;
    }
  } else {
    if((ep = ename(path)) == 0){
      return -1;
    }
    elock(ep);
    if((ep->attribute & ATTR_DIRECTORY) && omode != O_RDONLY){
      eunlock(ep);
      eput(ep);
      return -1;
    }
  }

  if((f = filealloc()) == 0){
    eunlock(ep);
    eput(ep);
    return -1;
  }

  f->type = FD_ENTRY;
  f->off = 0;
  f->ep = ep;
  f->readable = !(omode & O_WRONLY);
  f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
  
  if ((fd = fdalloc(f)) < 0){
    eunlock(ep);
    fileclose(f);
    return -1;
  }

  eunlock(ep);

  return fd;
}
/* 
  int openat(int dirfd, const char *pathname, int flags, mode_t mode);
  DESCRIPTION:

    openat()
        The openat() system call operates in exactly the same way as
        open(), except for the differences described here.

        If the pathname given in pathname is relative, then it is
        interpreted relative to the directory referred to by the file
        descriptor dirfd (rather than relative to the current working
        directory of the calling process, as is done by open() for a
        relative pathname).

        If pathname is relative and dirfd is the special value AT_FDCWD,
        then pathname is interpreted relative to the current working
        directory of the calling process (like open()).

        If pathname is absolute, then dirfd is ignored.

    flags:

    The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.  
    These request opening the file read-only, write-only, or read/write, respectively.
    
    In addition, zero or more file creation flags and file status flags can be bitwise-or'd in flags.
    
    The distinction between these two groups of flags is that the file creation flags affect the
    semantics of the open operation itself, while the file status flags affect the semantics of subsequent I/O operations. 

    File creation flags: O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMPFILE, and O_TRUNC. 

    Here I list some important flags:

    O_APPEND
          The file is opened in append mode.  Before each write(2),
          the file offset is positioned at the end of the file, as
          if with lseek(2).  The modification of the file offset and
          the write operation are performed as a single atomic step.
    O_DIRECTORY
          If pathname is not a directory, cause the open to fail.
    O_CREAT
          If pathname does not exist, create it as a regular file.
          The mode argument specifies the file mode bits to be
          applied when a new file is created.  If neither O_CREAT
          nor O_TMPFILE is specified in flags, then mode is ignored
          (and can thus be specified as 0, or simply omitted).  The
          mode argument must be supplied if O_CREAT or O_TMPFILE is
          specified in flags; if it is not supplied, some arbitrary
          bytes from the stack will be applied as the file mode.
    O_TRUNC
          If the file already exists and is a regular file and the
          access mode allows writing (i.e., is O_RDWR or O_WRONLY)
          it will be truncated to length 0.  If the file is a FIFO
          or terminal device file, the O_TRUNC flag is ignored.
          Otherwise, the effect of O_TRUNC is unspecified.
 */
uint64
sys_openat(void)
{
  char path[MAXPATH + 1];
  int fd, flags;
  struct file *f;
  struct dirent *ep;
  int dirfd; 
  if (argint(0, &dirfd) < 0 || argstr(1, path, MAXPATH) < 0 || argint(2, &flags) < 0) {
    return -1;
  }
  // printf("path = %s\n", path);
  // printf("flags = %d\n", flags); 
  // printf("flags & O_CREATE = %d\n", flags & O_CREAT);
  // printf("flags & O_RDONLY = %d\n", flags & O_RDONLY);
  // printf("flags & O_WRONLY = %d\n", flags & O_WRONLY);
  // printf("flags & O_RDWR = %d\n", flags & O_RDWR);
  // printf("flags & O_APPEND = %d\n", flags & O_APPEND);
  /* 
  TODO 
    I do nothing for O_TRUNC. For normal open (without O_APPEND), I just let it write from the begining.
    This will cause a problem: 
    If I first write lots of words into a file, next time I write serval words with O_TRUNC,
    But only the former will be covered, latter won't be convered.
   */
  if (flags & O_CREAT) {
    // Until now it's not certain that create file is needed.
    // TODO we should ename(dir/path) not ename(path)
    if ((ep = ename(path)) == 0) {
      // No-exist file
      if (dirfd == AT_FDCWD) { 
        // Create file relative to current process working dir
        ep = create(path, T_FILE);
      }
      else {
        // Create file relative to the path of dirfd
        // first: find dirfd's dir entry
        struct proc *p = myproc();
        struct file * dirfile = p -> ofile[dirfd];
        struct dirent * newcwd = dirfile -> ep;
        // save old cwd
        struct dirent * oldcwd = p -> cwd;
        char oldname[FAT32_MAX_FILENAME + 1];
        strncpy(oldname, oldcwd->filename, FAT32_MAX_FILENAME + 1);
        p -> cwd = newcwd;
        ep = create(path, T_FILE);
        if (ep == 0) {
          __debug_info("openat, create new file(dir != AT_FDCWD) failed, the most possible reason is a old mnt exists\n");
          p -> cwd = oldcwd;
          return -1;
        }
        p -> cwd = oldcwd;
      }
    }
    else{
      // Exist File, so we goto normal open.
      goto NORMAL_OPEN;
    }
  }
  else {
  NORMAL_OPEN:
    // open without create
    // printf("open without create\n");
    if ((ep = ename(path)) == 0) {
      return -1;
    }
    // TODO fix this
    if (path[0] == '.' && path[1] == '\0') {
      (ep->filename)[0] = '.';
    }
    elock(ep);
  }

  if((f = filealloc()) == 0){
    eunlock(ep);
    eput(ep);
    return -1;
  }

  f->type = FD_ENTRY;
  f->off = 0;
  f->ep = ep;
  f->readable = !(flags & O_WRONLY);
  f->writable = (flags & O_WRONLY) || (flags & O_RDWR);
  
  if (flags & O_APPEND) {
    f->off = f->ep->file_size;
  }

  if ((fd = fdalloc(f)) < 0){
    eunlock(ep);
    fileclose(f);
    return -1;
  }
  eunlock(ep);
  return fd;
}


uint64
sys_mkdir(void)
{
  char path[FAT32_MAX_PATH]; // 设置这个path的目的是把用户空间的参数拷贝到内核空间内，也就是拷贝到path处。
  struct dirent *ep;

  // 第二个参数，因为系统调用是这样的形式：syscall(SYS_mkdirat, AT_FDCWD, path, mode);
  // 这里改变了之后说明xv6-user/下的已经不能再用。
  
  if(argstr(1, path, FAT32_MAX_PATH) < 0){
    return -1;
  }
  // printf("new dir's path = %s\n", path);
  if((ep = create(path, T_DIR)) == 0){
    // printf("Create new dir failed\n");
    return -1;
  }

  eunlock(ep);
  eput(ep);
  return 0;
}

// uint64
// sys_mknod(void)
// {
//   struct dirent *ep;
//   char path[FAT32_MAX_PATH];
//   int major, minor;

//   if((argstr(0, path, FAT32_MAX_PATH)) < 0 ||
//      argint(1, &major) < 0 ||
//      argint(2, &minor) < 0 ||
//      (ep = create(path, T_DEVICE, major, minor)) == 0){
//     return -1;
//   }
//   eunlock(ep);
//   eput(ep);
//   return 0;
// }

uint64
sys_chdir(void)
{
  // test-user: syscall(SYS_chdir, path);
  //                               0
  // xv6-test: chdir("dir0")
  //                  0

  char path[MAXPATH];
  struct dirent *ep;
  struct proc *p = myproc();
  
  if(argstr(0, path, MAXPATH) < 0 || (ep = ename(path)) == 0){
    return -1;
  }

  elock(ep);
  if(!(ep->attribute & ATTR_DIRECTORY)){
    eunlock(ep);
    eput(ep);
    return -1;
  }
  eunlock(ep);

  eput(p->cwd);
  p->cwd = ep;

  return 0;
}

uint64
sys_exec(void)
{
  char path[MAXPATH], *argv[MAXARG];
  int i;
  uint64 uargv, uarg;
  // uargv is a pointer to userspace argv[], which in a1.
  if(argstr(0, path, MAXPATH) < 0 || argaddr(1, &uargv) < 0){
    return -1;
  }
  memset(argv, 0, sizeof(argv));
  for(i=0;; i++){
    if(i >= NELEM(argv)){
      goto bad;
    }
    if(fetchaddr(uargv+sizeof(uint64)*i, (uint64*)&uarg) < 0){
      goto bad;
    }
    if(uarg == 0){
      argv[i] = 0;
      break;
    }
    argv[i] = kalloc();
    if(argv[i] == 0)
      goto bad;
    if(fetchstr(uarg, argv[i], PGSIZE) < 0)
      goto bad;
  }

  int ret = exec(path, argv);

  for(i = 0; i < NELEM(argv) && argv[i] != 0; i++)
    kfree(argv[i]);

  return ret;

 bad:
  for(i = 0; i < NELEM(argv) && argv[i] != 0; i++)
    kfree(argv[i]);
  return -1;
}

uint64
sys_pipe(void)
{
  uint64 fdarray; // user pointer to array of two integers
  struct file *rf, *wf;
  int fd0, fd1;
  struct proc *p = myproc();
  // 
  // test-user: syscall(SYS_pipe2, fd, 0);
  //                               0   1
  // xv6-test: int fds[2]; pipe(fds)
  //                             0 
  if(argaddr(0, &fdarray) < 0)
    return -1;
  if(pipealloc(&rf, &wf) < 0)
    return -1;
  fd0 = -1;
  if((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0){
    if(fd0 >= 0)
      p->ofile[fd0] = 0;
    fileclose(rf);
    fileclose(wf);
    return -1;
  }
  if(copyout(p->pagetable, fdarray, (char*)&fd0, sizeof(fd0)) < 0 ||
     copyout(p->pagetable, fdarray+sizeof(fd0), (char *)&fd1, sizeof(fd1)) < 0){
    p->ofile[fd0] = 0;
    p->ofile[fd1] = 0;
    fileclose(rf);
    fileclose(wf);
    return -1;
  }
  return 0;
}

// To open console device.
uint64
sys_dev(void)
{
  int fd, omode;
  int major, minor;
  struct file *f;

  if(argint(0, &omode) < 0 || argint(1, &major) < 0 || argint(2, &minor) < 0){
    return -1;
  }

  if(omode & O_CREATE_XV6){
    panic("dev file on FAT");
  }

  if(major < 0 || major >= NDEV)
    return -1;

  if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
    if(f)
      fileclose(f);
    return -1;
  }

  f->type = FD_DEVICE;
  f->off = 0;
  f->ep = 0;
  f->major = major;
  f->readable = !(omode & O_WRONLY);
  f->writable = (omode & O_WRONLY) || (omode & O_RDWR);

  return fd;
}

// To support ls command
uint64
sys_dir(void)
{
  struct file *f;
  uint64 p;

  if(argfd(0, 0, &f) < 0 || argaddr(1, &p) < 0)
    return -1;
  return dirnext(f, p);
}

// get absolute cwd string
uint64
sys_getcwd(void)
{
  // test-user: syscall(SYS_getcwd, buf, size);
  //                                 0     1                   
  // xv6-user: getcwd(char * mycwd);
  //                     0 
  uint64 addr;
  if (argaddr(0, &addr) < 0)
    return -1;
  __debug_info("getcwd", "buf addr = 0x%x\n", addr);
  struct dirent *de = myproc()->cwd;
  char path[MAXPATH];
  char *s;
  int len;

  if (de->parent == 0) {
    s = "/";
  } else {
    s = path + MAXPATH - 1;
    *s-- = '\0';
    while (de->parent) {
      len = strlen(de->filename);
      s -= len;
      if (s <= path)          // can't reach root "/"
        return -1;
      strncpy(s, de->filename, len);
      *--s = '/';
      de = de->parent;
    }
  }

  if (copyout(myproc()->pagetable, addr, s, strlen(s) + 1) < 0)
    return -1;
  
  return strlen(s)+1;

}

// Is the directory dp empty except for "." and ".." ?
static int
isdirempty(struct dirent *dp)
{
  struct dirent ep;
  int count;
  int ret;
  memset(&ep, 0, sizeof(ep));
  ret = enext(dp, &ep, 2 * 32, &count);   // skip the "." and ".."
  return ret == -1;
}

uint64
sys_remove(void)
{
  char path[FAT32_MAX_PATH];
  struct dirent *ep;

  if(argstr(0, path, MAXPATH) < 0)
    return -1;
  if((ep = ename(path)) == 0){
    return -1;
  }
  elock(ep);

  if(ep->ref != 1 ||
    ((ep->attribute & ATTR_DIRECTORY) && !isdirempty(ep))){
      eunlock(ep);
      eput(ep);
      return -1;
  }
  ep->valid = 2;    // remove mark, eput will calls eupdate(), then etrunc()

  eunlock(ep);
  eput(ep);

  return 0;
}
#define AT_FDCWD -100
uint64 sys_unlinkat(void)
{
  // printf("...\n");
  char path[FAT32_MAX_PATH];
  struct dirent *ep;
  int flag;
  if(argint(0, &flag) < 0) return -1;
  if(argstr(1, path, MAXPATH) < 0)
    return -1;
  // printf("path = %s\n", path);
  if((ep = ename(path)) == 0){
    return -1;
  }
  elock(ep);

  if(ep->ref != 1 ||
    ((ep->attribute & ATTR_DIRECTORY) && !isdirempty(ep))){
      eunlock(ep);
      eput(ep);
      return -1;
  }
  ep->valid = 2;    // remove mark, eput will calls eupdate(), then etrunc()
  // printf("ttt\n");
  eunlock(ep);
  eput(ep);

  return 0;
}
uint64 sys_mmap(void)
{
    /*
    test-user: syscall(SYS_mmap, start, len, prot, flags, fd, off);
    The starting address for the new mapping is specified in addr
    The length argument specifies the length of the mapping.
    If addr is NULL, then the kernel chooses the address at which to create the mapping; 
    this is the most portable method of creating a new mapping.
    If addr is not NULL, then the kernel takes it as a hint about where to place the mapping; 
    on Linux, the mapping will be created at a nearby page boundary. 
    The address of the new mapping is returned as the result of the call.
    The contents of a file mapping (as opposed to an anonymous mapping; see MAP_ANONYMOUS below), 
    are initialized using length bytes starting at offset offset in the file (or other object) referred to by the file descriptor fd. 
    offset must be a multiple of the page size as returned by sysconf(_SC_PAGE_SIZE).
    The prot argument describes the desired memory protection of the mapping (and must not conflict with the open mode of the file). 
    It is either PROT_NONE or the bitwise OR of one or more of the following flags:
      PROT_EXEC
        Pages may be executed.
      PROT_READ
        Pages may be read.
      PROT_WRITE
        Pages may be written.
      PROT_NONE
        Pages may not be accessed.
    The flags argument determines whether updates to the mapping are visible to other processes mapping the same region, and whether updates are carried through to the underlying file. 
    This behavior is determined by including exactly one of the following values in flags:
      MAP_SHARED
      MAP_PRIVATE
      MAP_ANONYMOUS
        The mapping is not backed by any file; its contents are initialized to zero. The fd and offset arguments are ignored; however, some implementations require fd to be -1 if MAP_ANONYMOUS (or
    a0  start           0x0      0
    a1  len             0x1b1000 1773568
    a2  prot            0x3      3
    a3  flags           0x22     34
    a4  fd              -1       18446744073709551615
    a5  off             0x0      0
    a6  off             0x0      0
    a7  call_number     0xde     222
    */
    uint64 start;
    int len, prot, flags, fd, off;
    if (argaddr(0, &start) < 0) {
      __debug_warn("mmap", "start1\n");
      return -1;
    }
    if (start != 0) {
      __debug_warn("mmap", "start2\n");
    }
    if (argint(1, &len) < 0) {
      __debug_warn("mmap", "len\n");
      return -1;
    }
    if (argint(2, &prot) < 0) {
      __debug_warn("mmap", "prot\n");
      return -1;
    }
    if (argint(3, &flags) < 0) {
      __debug_warn("mmap", "flags\n");
      return -1;
    }
    if(argint(4, &fd) < 0) {
      __debug_warn("mmap", "fd\n");
      return -1;
    }
    
    #define MAP_ANONYMOUS 0x20
    if (flags & MAP_ANONYMOUS) {
      uint64 oldsz = myproc()->sz;
      __debug_info("mmap", "MAP_ANONYMOUS\n");
      __debug_info("mmap", "len = 0x%x\n", len);
      uint64 sz = PGROUNDUP(oldsz);
      growproc(sz-oldsz);
      if (growproc(len) < 0) {
        __debug_error("mmap", "grow proc error\n");
        return -1;
      }
      return sz;
    }
    else {
      struct file *f = myproc()->ofile[fd];
      if(argint(5, &off) < 0) {
        __debug_warn("mmap", "off\n");
        return -1;
      }
      printf("start = 0x%x, len = 0x%x, prot = %d, flags = %d, fd = %d, off = %d\n", start, len, prot, flags, fd, off);
      return mmap(f->ep, f->off);
    }
}
uint64 sys_munmap(void)
{
    // test-user: syscall(SYS_munmap, start, len);
    uint64 start;
    int len;
    if(argaddr(0, &start) < 0) return -1;
    if(start == 0) printf("NULL address\n");
    if(argint(1, &len) < 0) return -1;
    return munmap(start);
}
uint64 sys_mount(void)
{
  char dev_path[FAT32_MAX_PATH], mount_point[FAT32_MAX_PATH], fstype[FS_MAX_TYPE];
  int flags;
  uint64 data;
  if(argstr(0, dev_path, FAT32_MAX_PATH) < 0 || argstr(1, mount_point, FAT32_MAX_PATH) < 0 ||
    argstr(2, fstype, FS_MAX_TYPE) < 0 || argint(3, &flags) < 0 || argaddr(4, &data) < 0){
      return -1;
  }
  do_mount(dev_path, mount_point);
  return 0;
}
uint64 sys_umount2(void)
{
  char mount_point[FAT32_MAX_PATH];
  int flags;
  if(argstr(0, mount_point, FAT32_MAX_PATH) < 0 ||  argint(1, &flags) < 0){
    return -1;
  }
  do_umount(mount_point);
  return 0;
}

uint64 sys_readlinkat(void)
{
  int        dirfd, n;
  char       filename[MAXPATH];
  const char s[] = "/busybox_unstripped_lcc";
  uint64 ubuf;
  if (argint(0, &dirfd) < 0 || argstr(1, filename, MAXPATH) < 0)
    return -1;
  if (argaddr(2, &ubuf) < 0 || argint(3, &n))
    return -1;
  __debug_info("readlinkat", "buf addr = 0x%x\n", ubuf);
  copyout(myproc()->pagetable, ubuf, (char *)s, sizeof(s) + 1);
  return sizeof(s);
}

uint64 
sys_sendfile(void)
{
  /* ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); */
  int out_fd, in_fd, offset, count;
  struct file * out_f, * in_f;
  if (argfd(0, &out_fd, &out_f) < 0 || argfd(1, &in_fd, &in_f) < 0 || argint(2, &offset) < 0 || argint(3, &count) < 0) {
    return -1;
  }
  /* read from in_f */
  char * tmp = kalloc();
  fileread_kernel(in_f, (uint64)tmp, in_f->ep->file_size);
  /* write to out_f */
  filewrite_kernel(out_f, (uint64)tmp, in_f->ep->file_size);
  kfree(tmp);
  /* TODO
    When return val > 0, we get unlimited output.
    But https://www.man7.org/linux/man-pages/man2/sendfile.2.html tell us return the number of bytes written to out_fd.
    I guess it's related to f->off: Linux may change f->off in sendfile.
    Now I just return 0 to get correct display (command: cat).
   */
  return 0;
}

uint64
sys_fcntl(void)
{
  // int fcntl(int fd, int cmd, ... /* arg */ );
  int fd, cmd, arg;
  struct file * f;
  if (argfd(0, &fd, &f) < 0 || argint(1, &cmd) < 0 || argint(2, &arg) < 0) {
    return -1;
  }
  __debug_info("fnctl", "fd = %d, cmd = %d\n", fd, cmd);
  if (cmd == F_DUPFD_CLOEXEC) {
    struct proc *p = myproc();
    int k;
    for (k = arg; k < NOFILE; k++) {
      if (p->ofile[k] == 0) {
        p->ofile[k] = f;
        break;
      }
    }
    filedup(f);
    return k;
  }
  else {
    // TODO support more cmd type
    __debug_error("fcntl", "support only F_DUPFD_CLOEXEC\n");
    return -1;
  }
}